//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this sample source code is subject to the terms of the Microsoft
// license agreement under which you licensed this sample source code. If
// you did not accept the terms of the license agreement, you are not
// authorized to use this sample source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the LICENSE.RTF on your install media or the root of your tools installation.
// THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
//
// (C) Copyright 2006 Marvell International Ltd.
// All Rights Reserved
//

/* Copyright ?1999,2000,2001 Intel Corp.  */
/*++

Module Name:

  pwrbutton.c

Abstract:

  This module is intended as a home for threads that monitor and manage ULDR suspend/resume
  on the platform. Current implementation monitors SW6, EXT_WAKE<0> on the Littleton
  main board.

Notes:


--*/

#include <windows.h>
#include <windev.h>
#include <pm.h>
#include <bsp.h>
#include <xllp_intc.h>
#include <xllp_pm.h>

#define QUEUE_ENTRIES   16
#define MAX_NAMELEN     64
#define QUEUE_SIZE      (QUEUE_ENTRIES*(sizeof(POWER_BROADCAST)+MAX_NAMELEN))

#define PECR_E0IS (1u << 29)
#define PECR_E0IE (1u << 28)

BOOL   g_FlagExitThrd = FALSE;
HANDLE g_ButtonThread = NULL;
HANDLE g_PwrMonThread = NULL;
HANDLE g_PwrMonThreadExitEvent = NULL;

HANDLE g_SuspendEvent = NULL;
HANDLE g_SuspendKeyEvent = NULL;

UINT32 g_SysIntrPwrbtn = 0;
volatile XLLP_PM_SPMU_T *g_pSPMReg = NULL;

// Power state monitor thread
INT WINAPI PwrMonThread(void)
{
    HANDLE hNotifications;
    HANDLE hReadMsgQ;
    HANDLE hWakeEvents[2];
    MSGQUEUEOPTIONS msgOptions = {0}; 

    // Create a message queue for Power Manager notifications.
    msgOptions.dwSize        = sizeof(MSGQUEUEOPTIONS);
    msgOptions.dwFlags       = 0;
    msgOptions.dwMaxMessages = QUEUE_ENTRIES;
    msgOptions.cbMaxMessage  = sizeof(POWER_BROADCAST) + MAX_NAMELEN;
    msgOptions.bReadAccess   = TRUE;

    hReadMsgQ = CreateMsgQueue(NULL, &msgOptions);
    if ( hReadMsgQ == NULL )
    {
        DEBUGMSG(1, (TEXT("PWM: CreateMsgQueue(Read): error %d\r\n"), GetLastError()));
        g_FlagExitThrd = TRUE;
    }

    // Request Power notifications
    hNotifications = RequestPowerNotifications(hReadMsgQ, POWER_NOTIFY_ALL);
    if ( ! hNotifications ) 
    {
        DEBUGMSG(1, (TEXT("PWM: RequestPowerNotifications: error %d\r\n"), GetLastError()));
        g_FlagExitThrd = TRUE;
    }

    hWakeEvents[0] = hReadMsgQ;
    hWakeEvents[1] = g_PwrMonThreadExitEvent;

    while(!g_FlagExitThrd)
    {
        int   iBytesInQueue = 0;
        DWORD dwFlags;
        DWORD dwResult;
        UCHAR buf[QUEUE_SIZE];
        PPOWER_BROADCAST pB = (PPOWER_BROADCAST) buf;

        memset(buf, 0, QUEUE_SIZE);
                
        DEBUGMSG(1, (TEXT("PWM: Waiting for PM state transition notification\r\n")));

        dwResult = WaitForMultipleObjects(2, hWakeEvents, FALSE, INFINITE );
        if( g_FlagExitThrd ) break;

        switch (dwResult) {
            case WAIT_OBJECT_0:
                //Item in message queue
                break;
            case WAIT_OBJECT_0 + 1:
                //This thread has been signalled to exit
                ASSERT( TRUE == g_FlagExitThrd );
                break;
            default:
                //Will never get here
                ASSERT(FALSE);
        }
            
        // Read message from queue.
        if ( ! ReadMsgQueue(hReadMsgQ, buf, QUEUE_SIZE, &iBytesInQueue, 1000, &dwFlags) )
        {
            DEBUGMSG(1, (TEXT("PWM: ReadMsgQueue: ERROR:%d\r\n"), GetLastError()));
        } 
        else if ( iBytesInQueue < sizeof(POWER_BROADCAST) )
        {
            DEBUGMSG(1, (TEXT("PWM: Received short message: %d bytes, expected: %d\r\n"),
                                  iBytesInQueue, sizeof(POWER_BROADCAST)));
        }
        else 
        {
            switch ( pB->Message )
            {
                case PBT_TRANSITION:
    
                    RETAILMSG(1, (TEXT("PWM: PBT_TRANSITION to system power state [Flags: 0x%x]: '%s'\r\n"),
                                  pB->Flags, pB->SystemPowerState));
    
                    switch ( POWER_STATE(pB->Flags) )
                    {
                        case POWER_STATE_ON:
                            DEBUGMSG(1, (TEXT("PWM: POWER_STATE_ON\r\n")));
                            break;
    
                        case POWER_STATE_OFF:
                            DEBUGMSG(1, (TEXT("PWM: POWER_STATE_OFF\r\n")));
                            break;
    
                        case POWER_STATE_CRITICAL:
                            DEBUGMSG(1, (TEXT("PWM: POWER_STATE_CRITICAL\r\n")));
                            break;
    
                        case POWER_STATE_BOOT:
                            DEBUGMSG(1, (TEXT("PWM: POWER_STATE_BOOT\r\n")));
                            break;
    
                        case POWER_STATE_IDLE:
                            DEBUGMSG(1, (TEXT("PWM: POWER_STATE_IDLE\r\n")));
                            break;
    
                        case POWER_STATE_SUSPEND:
                            DEBUGMSG(1, (TEXT("PWM: POWER_STATE_SUSPEND\r\n")));
                            break;
    
                        case POWER_STATE_RESET:
                            DEBUGMSG(1, (TEXT("PWM: POWER_STATE_RESET\r\n")));
                            break;
    
                        case POWER_STATE_PASSWORD:
                            DEBUGMSG(1, (TEXT("PWM: POWER_STATE_PASSWORD\r\n")));
                            break;
    
                        case 0:
                            DEBUGMSG(1,(TEXT("PWM: Power State Flags:0x%x\r\n"),pB->Flags));
                            break;
    
                        default:
                            DEBUGMSG(1,(TEXT("PWM: Unknown Power State Flags:0x%x\r\n"),pB->Flags));
                            break;
                    }
                    break;

                case PBT_RESUME:
                {
                    RETAILMSG(1, (TEXT("PWM: PBT_RESUME\r\n")));

                    // If GPIO1 resumed the system set the system state to ON
                    {
			//SetSystemPowerState(NULL, POWER_STATE_ON, POWER_FORCE);
                        
			DWORD wakeSrc = SYSWAKE_UNKNOWN;
                        DWORD bytesRet= 0;
                        if (KernelIoControl(IOCTL_HAL_GET_WAKE_SOURCE, NULL, 0, &wakeSrc, sizeof(wakeSrc), &bytesRet) &&
                           (bytesRet == sizeof(wakeSrc))) 
			{
                                if (SYSWAKE_POWER_BUTTON == wakeSrc) 
				{
				    RETAILMSG(1, (TEXT("PWM: Set System status ON\r\n")));
                                    SetSystemPowerState(NULL, POWER_STATE_ON, POWER_FORCE);
                           	}
				else
				{
				    NKDbgPrintfW(L"PWM: Wakeup source %d\r\n",wakeSrc );
				}
                        }
                        else {
                            NKDbgPrintfW(L"PWM: Error getting wake source\r\n");
                        }
                    }
            
                    break;
                }       
                case PBT_POWERSTATUSCHANGE:
                    RETAILMSG(1, (TEXT("PWM: PBT_POWERSTATUSCHANGE\r\n")));
                    break;
                        
                case PBT_POWERINFOCHANGE:
                {
                    PPOWER_BROADCAST_POWER_INFO ppbpi = (PPOWER_BROADCAST_POWER_INFO) pB->SystemPowerState;
    
                    RETAILMSG(1, (TEXT("PWM: PBT_POWERINFOCHANGE\r\n")));
                    RETAILMSG(1, (TEXT("PWM: \tAC line status %u, battery flag %u, backup flag %u, %d levels\r\n"),
                            ppbpi->bACLineStatus, ppbpi->bBatteryFlag, 
                            ppbpi->bBackupBatteryFlag, ppbpi->dwNumLevels));
                    break;
                }
                        
                default:
                    DEBUGMSG(1, (TEXT("PWM: Unknown Message:%d\r\n"), pB->Message));
                    break;
            }
        }
    }

    if ( hNotifications )
    {
        StopPowerNotifications(hNotifications);
        hNotifications = NULL;
    }

    if ( hReadMsgQ )
    {
        CloseMsgQueue(hReadMsgQ);
        hReadMsgQ = NULL;
    }

    return 0;
}

// Power Button event thread
INT WINAPI ButtonThread(void)
{
    DWORD dwResult;
    
    while(!g_FlagExitThrd)
    {
        dwResult = WaitForSingleObject(g_SuspendKeyEvent, INFINITE);
        if(g_FlagExitThrd) break;

        switch(dwResult) {
            case WAIT_OBJECT_0:
                // Interrupt from the Suspend Key GPIO being pressed!

                // "Debounce" the GPIO by waiting until the button is up
                while(g_pSPMReg->pecr & 1) {;}

                // If this driver is loaded, LPInstall wants notification of the suspend request.
                SetEvent( g_SuspendEvent );

                // Clear Interrupt status
                g_pSPMReg->pecr |= PECR_E0IS;

                // Done handling SYSINTR
                InterruptDone(g_SysIntrPwrbtn);

                break;
            case WAIT_OBJECT_0 + 1:
                // This thread has been signalled to exit
                ASSERT( TRUE == g_FlagExitThrd );
                break;
            default:
                // Will never get here
                ASSERT(FALSE);
                break;
        }
    }

    return 0;
}

BOOL WINAPI DllEntry(HANDLE hInstDll, DWORD dwReason, LPVOID lpvReserved)
{
    switch (dwReason)
    {
        case DLL_PROCESS_ATTACH:
            DEBUGMSG(1, (TEXT("DllEntry: DLL_PROCESS_ATTACH (PID=x%08x)\r\n"), GetCurrentProcessId()));
            break;
    
        case DLL_PROCESS_DETACH:
            DEBUGMSG(1, (TEXT("DllEntry: DLL_PROCESS_DETACH (PID=x%08x)\r\n"), GetCurrentProcessId()));
            break;
    }
    return (TRUE);
}

VOID PWB_PowerUp(VOID) 
{
    DEBUGMSG(1, (TEXT("PWB_PowerUp:\r\n")));
    g_pSPMReg->pecr |= PECR_E0IE;	// enable interrupt for EXT_WAKE[0], corresponding to SW6 on Littleton
    return;
}

BOOL PWB_PowerDown(VOID) 
{
    DEBUGMSG(1, (TEXT("PWB_PowerDown:\r\n")));
    g_pSPMReg->pecr &= ~PECR_E0IE;	// disable interrupt for EXT_WAKE[0]
    return(TRUE);
}

BOOL PWB_Deinit(DWORD p1) 
{
    DEBUGMSG(1, (TEXT("PWB_DeInit(x%08x):\r\n"), p1));

    // Notify threads to exit
    g_FlagExitThrd = TRUE;

    // Disable HW interrupt and deregister g_SuspendKeyEvent
    InterruptDisable(g_SysIntrPwrbtn);

    // Manually trigger g_SuspendKeyEvent to signal ButtonThread to exit
    SetEvent(g_SuspendKeyEvent);

    if(g_ButtonThread)
    {
        switch( WaitForSingleObject(g_ButtonThread, 5000) ) {
            case WAIT_OBJECT_0:
                DEBUGMSG(1, (L"Power Button thread: normal exit\n"));
                break;
            case WAIT_TIMEOUT:
                DEBUGMSG(1, (L"Timeout waiting for Power Button thread to exit\n"));
                break;
        }
        CloseHandle(g_ButtonThread);
        g_ButtonThread=NULL;
    } 	

    SetEvent(g_PwrMonThreadExitEvent);

    if(g_PwrMonThread)
    {	
        switch( WaitForSingleObject(g_PwrMonThread, 5000) ) {
            case WAIT_OBJECT_0:
                DEBUGMSG(1, (L"Power Monitor thread: normal exit\n"));
                break;
            case WAIT_TIMEOUT:
                DEBUGMSG(1, (L"Timeout waiting for Power Monitor thread to exit\n"));
                break;
        }
        CloseHandle(g_PwrMonThread);
        g_PwrMonThread=NULL;
    }	

    if(g_SuspendKeyEvent)
    {
        CloseHandle(g_SuspendKeyEvent);
        g_SuspendKeyEvent = NULL;
    }

    if (g_SuspendEvent)
    {
        CloseHandle(g_SuspendEvent);
        g_SuspendEvent = NULL;
    }

    if(g_PwrMonThreadExitEvent)
    {
        CloseHandle(g_PwrMonThreadExitEvent);
        g_PwrMonThreadExitEvent = NULL;
    }

    return (TRUE);
}

DWORD PWB_Init(DWORD p1, DWORD p2)
{
    BOOL result;
    DWORD extwake_irq = XLLP_INTC_S_EXT1;
    PHYSICAL_ADDRESS ioPhysicalBase = {MONAHANS_BASE_REG_PA_SPMU, 0 };

    DEBUGMSG(1, (TEXT("PWB_Init(x%08x,x%08x):\r\n"), p1, p2));

    g_pSPMReg = (XLLP_PM_SPMU_T *)MmMapIoSpace(ioPhysicalBase, sizeof(g_pSPMReg), FALSE);
    
    // Grab the named event LPSUSPEND that LPInstall.exe is waiting on
    g_SuspendEvent = CreateEvent( NULL, FALSE, FALSE, L"LPSUSPEND" );
    if( NULL == g_SuspendEvent )
    { 
        DEBUGMSG(1, (TEXT("PWB_Init: Failed to create LPSUSPEND event\r\n")));
        goto CleanUp;
    }

    // This event means the suspend key was pressed on the Littleton board
    g_SuspendKeyEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
    if( NULL == g_SuspendKeyEvent )
    {
        DEBUGMSG(1, (TEXT("PWB_Init: Failed to create Suspend Key event\r\n")));
        goto CleanUp;
    }

    g_PwrMonThreadExitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    if( NULL == g_PwrMonThreadExitEvent )
    {
        DEBUGMSG(1, (TEXT("PWB_Init: Failed to create PwrMonThread Exit Event\r\n")));
        goto CleanUp;
    }

    g_ButtonThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) ButtonThread, NULL, 0, NULL);
    if ( NULL == g_ButtonThread )
    {
        DEBUGMSG(1, (TEXT("PWB_Init: Failed to create Button Intterupt Thread\r\n")));
        goto CleanUp;
    }
        
    g_PwrMonThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) PwrMonThread, NULL, 0, NULL);
    if ( NULL == g_PwrMonThread )
    {
        DEBUGMSG(1, (TEXT("PWB_Init: Failed to create Power Monitor Thread\r\n")));
        goto CleanUp;
    }

    // Request the SYSINTR for the power button.
    result = KernelIoControl(IOCTL_HAL_REQUEST_SYSINTR, &extwake_irq, sizeof(UINT32), &g_SysIntrPwrbtn, sizeof(UINT32), NULL); 
    if(!result)
    {
        DEBUGMSG(1, (TEXT("PWB_Init: Failed to request SYSINTR\r\n")));        
        goto CleanUp;
    }

    if( !InterruptInitialize(g_SysIntrPwrbtn, g_SuspendKeyEvent, NULL, 0) )
    {
        DEBUGMSG(1, (TEXT("PWB_Init: Failed call to InterruptInitalize!\r\n")));
        goto CleanUp;
    }

    PWB_PowerUp();

    DEBUGMSG(1, (TEXT("PWB_Init: Success\r\n")));
    return(TRUE);

 CleanUp:

    PWB_Deinit(0);
    return (FALSE);
}

DWORD PWB_Open (DWORD dwData, DWORD dwAccess, DWORD dwShareMode) 
{
    DEBUGMSG(1, (TEXT("PWB_Open:\r\n")));
    return(0x12345678);
}

BOOL PWB_Close(DWORD dwData) 
{
    DEBUGMSG(1, (TEXT("PWB_Close:\r\n")));
    return (TRUE);
}

DWORD PWB_Read (DWORD dwData,  LPVOID pBuf, DWORD Len) 
{
    return (0);
}

DWORD PWB_Write(DWORD dwData, LPCVOID pBuf, DWORD Len) 
{
    return (0);
}

DWORD PWB_Seek (DWORD dwData, long pos, DWORD type) 
{
    return (DWORD)-1;
}

BOOL PWB_IOControl(DWORD hOpenContext,DWORD dwCode,PBYTE pBufIn,DWORD dwLenIn,PBYTE pBufOut,DWORD dwLenOut,PDWORD pdwActualOut)
{
    return(FALSE);
}
